package ca.ualberta.cs.BibTex;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.StringTokenizer;

//enum Month {NONE, Jan, Feb, Mar, Apr, May, Jun, July, Aug, Sep, Oct, Nov, Dec };

public class BibTex {
	String type = null;
	ArrayList<Author> authors = new ArrayList<Author>();
	String title ;
	String journal = null;
	String abstr = null; // do we need it in the bibtex body? I haven't added it to the parsing/toString part
							//answer: no! since it will take duplicate space in database (already saved as abstract attribute)
	Integer year = null;
	//Month month = Month.NONE;
	String month = null;
	Integer volume = null;
	Integer number = null;
	Integer fromPage = null;
	Integer toPage = null;
	HashSet<String> keywords = new HashSet<String>();
	String keywordsString = null;
	String DOI = null;
	String ISSN = null;
	String ISBN = null;
	String theWhole = null;
	String location = null;
	String address = null;
	String bookTitle = null;
	String note = null;
	String fullAuthorsInfo = null;

	final int MAX_ABSTR_LENGTH = 5000;
	final int MAX_TITLE_LENGTH = 300;
	final int MAX_BIBTEX_LENGTH = 1000;
	final int MAX_IDS_LENGTH = 50; //max DOI, ISSN, or ISBN lenght TODO
	
	public static String[] bibKeys = {"authors","title","journal","year","month",
		"volume","number","pages","keywords","DOI","ISSN","ISBN","location","address","bookTitle","note",};
	
	public BibTex(String tex){
		parse(tex);
	}
	
	public void setType(String t){
		type = t;
	}
	
	/*public void addAuthor(String nameString){
		authors.add(new Author().parse(nameString));
	}*/
	
	public void setAuthors(String authors){
		
		String[] partsArr = authors.split("and");
		ArrayList<String> parts = new ArrayList<String>();
		for( int i = 0 ; i < partsArr.length ; i++ )
			parts.add(partsArr[i]);
		
		for(int i = 0 ; i < parts.size() ; i++){
			String part = parts.get(i);
			if(part != null && part.trim().length() != 0 )
			{
				
				
				String[] nameParts = part.split(",");
				
				if(nameParts.length > 1 && nameParts[0].trim().contains(" ") && nameParts[1].trim().contains(" ")){ //if it wasn't originially separated with "and"
					for(int j = nameParts.length-1 ; j >= 1  ; j--)
						parts.add(i+1,nameParts[j]);
					String thisName = nameParts[0];
					nameParts = new String[1];
					nameParts[0] = thisName;
					part = thisName;
				}
				if(nameParts.length <= 1 || nameParts[0].trim().length() == 0 || nameParts[1].trim().length() == 0){ //first and last are not separated with ","
											//so, first/mid comes first
					nameParts = part.trim().split(" ");
					if(nameParts.length > 1 ){
						String last = nameParts[1];
						last = last.split(",")[0];
						nameParts[1] = nameParts[0];
						nameParts[0] = last;
					}
					else{
						nameParts = new String[2];
						nameParts[0] = part;
						nameParts[1] = "?";
					}
				}
				boolean dotFormat = true;
				
				if(!part.contains("."))
					dotFormat = false;
				
				if(dotFormat){
					String last = nameParts[0].trim();
					String fm = nameParts[1].trim();
					this.authors.add(new Author(fm,null,last));
				}
				else{
					String last = nameParts[0].trim();
					String first = nameParts[1].trim();
					this.authors.add(new Author(null,first,last));
				}
			}
		}
		
	}
	
	public void addAuthor(Author author){
		authors.add(author);
	}
	
	/**
	 * 
	 * @param name : author's full name in one of the following formats: last, first  or  last, f. 
	 */
	public void addAuthor(String name){
		setAuthors(name); //works if the 'authors' is not cleared in any other methods
	}
	
	public void setTitle(String title){
		if(title != null){
			title = title.substring(0,Math.min(title.length(), MAX_TITLE_LENGTH));
			this.title = title.replace("'", "`");			
		}
	}
	
	public void setJournal(String jrn){
		if(jrn != null)
			journal = jrn.split(",")[0].replace("'", "`");		
	}
	
	public void setMonth(String mon){
		if(mon != null && mon.length() >= 3)
			month = mon.substring(0,3).toLowerCase();
		else if(month == null)
			month = mon;
	}
	
	public void setPages(String pages){
		if(pages == null)
			return;
		
		String[] pgs = pages.split("-");
		if(pgs.length == 2){
			fromPage = Integer.parseInt(pgs[0].trim());
			toPage = Integer.parseInt(pgs[1].trim());
		}
		else if(pgs.length == 1)
			toPage = fromPage = Integer.parseInt(pgs[0].trim());
		else{
			int a = 0;
			for(int i = 0 ; i < pgs.length ; i++ ){
				if(pgs[i] == null || pgs[i].length() == 0)
					continue;
				a++;
				if(a==1)
					fromPage = Integer.parseInt(pgs[i].trim());
				else if(a==2)
					toPage = Integer.parseInt(pgs[i].trim());
				else 
					break;
			}
		}
			
	}
	
	public void setKeywords(String keys){
		if(keys == null)
			return;
		StringTokenizer tokenizer = new StringTokenizer(keys, ";,");
		while(tokenizer.hasMoreTokens()){
			String k = tokenizer.nextToken();
			if(k!=null && !k.equals(""))
				addKeyword(k.trim().replace("'", "`"));
		}
		keywordsString = keys;
	}
	
	public void addKeyword(String key){
		if( key != null )
			keywords.add(key);
	}
	
	public void setDoi(String doi){
		if(doi != null)
			DOI = doi.replace("'", "`");		
	}
	
	public void setISSN(String issn){
		if(issn != null)
			ISSN = issn.replace("'", "`");	
	}
	
	public void setYear(String val){
		try{
			if(val != null)
				year = Integer.parseInt(val);
		}catch(Exception e){e.printStackTrace();}
	}
	
	public void setNumber(String val){
		try{
			if(val != null)
				number = Integer.parseInt(val);	
		}catch(Exception e){e.printStackTrace();}
	}
	
	public void setVolume(String val){
		try{
			if(val != null)
				number = Integer.parseInt(val);
		}catch(Exception e){e.printStackTrace();}
	}
	
	public BibTex parse(String bib){		
		if(bib == null || bib.length() == 0)
			return null;
		
		theWhole = bib;
		
		if(bib.trim().startsWith("@")){
			bib = bib.trim().substring(1);
			StringTokenizer t1 = new StringTokenizer(bib,"{=}");
//			StringTokenizer t2 = new StringTokenizer(bib,",");
			setType(t1.nextToken().trim()); //between @ and "{=}" delims
		}
		
		String[] equalSplitted = bib.split("=");
		
		String val = null;
		String key = null;
		String part1 = null;
		String part2 = null;
		for(int i = 0 ; i < equalSplitted.length ; i++ ){
			if(part1 == null && i+1 < equalSplitted.length){
				part1 = equalSplitted[i];
				part2 = equalSplitted[i+1];
				i++;
			}
			else{
				part2 = equalSplitted[i];
			}
			
			StringTokenizer tpart1 = new StringTokenizer(part1,",. };");
			key = "";
			//last token
			while(tpart1.hasMoreTokens()){
				String temp = tpart1.nextToken().trim();
				if(temp != null && temp.length() != 0)
					key = temp;
			}
//			System.out.println("key:"+key+";");
			if(!part2.contains("\"")){
				String[] values1 = part2.split("\\{");
				//first token			
				val = values1[values1.length-1].split("}")[0];			
				
//				System.out.println("val:"+val+";");
	
				setKeyVal(key, val);
				
				String[] tp1 = part2.split("}");
				part1 = tp1[tp1.length-1];
			}
			else{
				String[] values1 = part2.split("\"");
				//before last '"'
				if(values1.length >= 2){
					val = values1[values1.length-2];
					part1 = values1[values1.length-1]; //after last '"'
				}
				else{
					val = values1[values1.length-1];
					part1 = null;
				}
				
//				System.out.println("val:"+val+";");
				setKeyVal(key, val);				
			}
		}
		return this;
	}

	public void setKeyVal(String key, String val){
		if(val == null || val.length() == 0 )
			return;
	  try{	
		if(key.equalsIgnoreCase("author") || key.equalsIgnoreCase("authors"))
			setAuthors(val);
		else if(key.equalsIgnoreCase("title"))
			setTitle(val);
		else if(key.equalsIgnoreCase("journal"))
			setJournal(val);
		else if(key.equalsIgnoreCase("doi"))
			setDoi(val);
		else if(key.equalsIgnoreCase("ISSN"))
			setISSN(val);
		else if(key.equalsIgnoreCase("keywords"))
			setKeywords(val);
		else if(key.equalsIgnoreCase("month"))
			setMonth(val);
		else if(key.equalsIgnoreCase("pages"))
			setPages(val);
		else if(key.equalsIgnoreCase("year"))
			setYear(val);
		else if(key.equalsIgnoreCase("ISBN"))
			setISBN(val);
		else if(key.equalsIgnoreCase("number"))
			setNumber(val);
		else if(key.equalsIgnoreCase("volume"))
			setVolume(val);
		else if(key.equalsIgnoreCase("location"))
			setLocation(val);
		else if(key.equalsIgnoreCase("address"))
			setAddress(val);
		else if(key.equalsIgnoreCase("booktitle"))
			setBookTitle(val);
		else if(key.equalsIgnoreCase("note"))
			setNote(val);
		
	  }catch(Exception e){
			System.out.println("An unsupported bibtex entry. Key: "+key+" ,Value(s): "+val);
			e.printStackTrace();
	  }
	}
	
	public String getISBN() {
		return ISBN;
	}

	public void setISBN(String iSBN) {
		if(iSBN != null)
			ISBN = iSBN.replace("'", "`");
	}

	public String getLocation() {
		return location;
	}

	public void setLocation(String location) {
		if(location != null)
			this.location = location.replace("'", "`");
	}

	public String getType() {
		return type;
	}

	public ArrayList<Author> getAuthors() {
		return authors;
	}

	public String getTitle() {
		return title;
	}

	public String getJournal() {
		return journal;
	}

	public Integer getYear() {
		return year;
	}

	public String getMonth() {
		return month;
	}

	public Integer getVolume() {
		return volume;
	}

	public Integer getNumber() {
		return number;
	}

	public Integer getFromPage() {
		return fromPage;
	}

	public Integer getToPage() {
		return toPage;
	}

	public HashSet<String> getKeywords() {
		return keywords;
	}

	public String getDOI() {
		return DOI;
	}

	public String getISSN() {
		return ISSN;
	}
	
	public String getTheWhole(){
		return theWhole;
	}
	
	public void setTheWhole(){
		theWhole = this.toString();
	}
	
	public void setTheWhole(String bib){
		if( bib != null)
			theWhole = bib;
	}

	public String getAddress() {
		return address;
	}

	public void setAddress(String address) {
		if(address != null)
			this.address = address.replace("'", "`");
	}

	public String getBookTitle() {
		return bookTitle;
	}

	public void setBookTitle(String bookTitle) {
		if(bookTitle == null )
			return;
		if(bookTitle.contains(":")){
			String[] t = bookTitle.split(":");
			if( t.length > 1 && t[1] != null){
				this.bookTitle = t[1].replace("'", "`");
				return;
			}
		}
		this.bookTitle = bookTitle.replace("'", "`");
	}

	public String getNote() {
		return note;
	}

	public void setNote(String note) {
		if(note != null)
			this.note = note.replace("'", "`");
	}
	
	@Override
	public String toString() {
		String bib = "@";
		bib += type + "{someid,\n" ;
		
		for(String key : bibKeys ){
			bib += key + "={";
			
			if(key.equals("pages")){
				if(fromPage != null)
					bib += fromPage + "-" + toPage;
				bib += "},\n";
				continue;
			}
			try{	
				if( this.getClass().getDeclaredField(key).get(this) == null ){
					bib += "},\n";
					continue;
				}
			} catch (Exception e) {					
				e.printStackTrace();
			}
			if(key.equals("authors")){
				for(int i = 0 ; i < authors.size() ; i++ )
					bib += authors.get(i).toStringForBib() + " and ";
				bib = bib.substring(0,bib.length()-5); //remove last and
			}
			else if(key.equals("keywords")){
//				for(String word : keywords )
//					bib += word + ";";
				bib += keywordsToString();
			}
			else
				try {
					bib += this.getClass().getDeclaredField(key).get(this);
				} catch (Exception e) {					
					e.printStackTrace();
				} 
			bib += "},\n";
		}		
		return bib + "}";
	}
	
	public String keywordsToString(){
		String keys = "";
		for(String word : keywords )
			keys += word + ";";
		return keys;
	}
	
	public void setAbstract(String abs){
		if(abs == null)
			return;
		abs = abs.substring(0,Math.min(abs.length(), MAX_ABSTR_LENGTH));
		abstr = abs;
	}
	
	public String getAbstract(){
		return abstr;
	}
	
	public String getFullAuthorsInfo() {
		return fullAuthorsInfo;
	}

	/**
	 * author names, in order that was extracted from bib or from pdf, separated with '; ' (ends with a '; '
	 */
	public String getAuthorNamesList(){
		String names = "";
		if(authors!=null){
			for(Author aut : authors){
				names += aut.toString()+"; ";
			}
		}
		return names;
	}
	
	public void setFullAuthorsInfo(String fullAuthorsInfo) {		
		
		if(fullAuthorsInfo == null || fullAuthorsInfo.length() == 0 )
			return;
		
		this.fullAuthorsInfo = fullAuthorsInfo;
		
		String authors = "";
		String emails = ""; //TODO
				
		String[] lines = fullAuthorsInfo.split("\n");
		if(lines.length < 1)
			return;
		
		String firstLine = lines[0];
		int i=0;
		while(firstLine == null || firstLine.length() == 0 ){
			if((++i) >= lines.length)
				return;			
			firstLine = lines[i];						
		}
		if(firstLine.contains(",") || firstLine.contains(" and ")){ //all authors in first row
			this.setAuthors(firstLine.trim());
			return;
		}
		authors += firstLine ;
		//assume: if authors are not all in one line. they are sorted like this: author1, affiliation1, email1, author2, ...
		boolean prewasEmail = false;
		for(int j = 1; j < lines.length ; j++){
			if(prewasEmail)
				authors += " and " + lines[j];
			if(lines[j].contains("@"))
				prewasEmail = true;
			else
				prewasEmail = false;
		}
		setAuthors(authors);
	}
	
	/*public static void main(String[] args) {
		ArrayList<Integer> test = new ArrayList<Integer>();
		test.add(1);
		test.add(3);
		test.add(5);
		for( int i = 0 ; i < test.size() ; i++){
			Integer t = test.get(i);
			if(!test.contains(t+1) && t+1 < 8)
				test.add(t+1);
			System.out.println(t);
		}
	}*/
	
}
